#include "QCefOsrWidget.h"
#include "QCefUtil.h"

QCefOsrWidget::QCefOsrWidget(QWidget * parent, Qt::WindowFlags flags)
    : QCefWidget{parent, flags}
{
    QCEF_DEBUG();

    //
    initOsr();
}

QCefOsrWidget::QCefOsrWidget(const QString & strUrl, QWidget * parent, Qt::WindowFlags flags)
    : QCefWidget{strUrl, parent, flags}
{
    QCEF_DEBUG();

    //
    initOsr();

    //m_strFirstUrl = strUrl;
    //createContainer();
    //createClient();
}

QCefOsrWidget::~QCefOsrWidget()
{
    QCEF_DEBUG();
}

int QCefOsrWidget::initOsr()
{
    //
    this->setBackgroundRole(QPalette::Window);
    this->setAttribute(Qt::WA_OpaquePaintEvent);
    this->setMouseTracking(true);
    this->setFocusPolicy(Qt::WheelFocus);

    //
    connect(this, &QCefOsrWidget::pageCursorChange, this, &QCefOsrWidget::onPageCursorChange);
    connect(this, &QCefOsrWidget::pagePopupShow, this, &QCefOsrWidget::onPagePopupShow);
    connect(this, &QCefOsrWidget::pagePopupSize, this, &QCefOsrWidget::onPagePopupSize);
    connect(this, &QCefOsrWidget::pagePaint, this, &QCefOsrWidget::onPagePaint);
    connect(this, &QCefOsrWidget::pageAcceleratedPaint, this, &QCefOsrWidget::onPageAcceleratedPaint);
    connect(this, &QCefOsrWidget::pageScrollOffsetChanged, this, &QCefOsrWidget::onPageScrollOffsetChanged);
    connect(this, &QCefOsrWidget::pageImeCompositionRangeChanged, this, &QCefOsrWidget::onPageImeCompositionRangeChanged);
    connect(this, &QCefOsrWidget::pageTextSelectionChanged, this, &QCefOsrWidget::onPageTextSelectionChanged);
    connect(this, &QCefOsrWidget::pageVirtualKeyboardRequested, this, &QCefOsrWidget::onPageVirtualKeyboardRequested);
    connect(this, &QCefOsrWidget::pageFocusedEditableNodeChanged, this, &QCefOsrWidget::onPageFocusedEditableNodeChanged);

    return 0;
}

int QCefOsrWidget::createClient()
{
    if (m_delegate == nullptr)
    {
        m_delegate = new QCefOsrDelegate(this);
        m_client = new QCefOsrClient(QCefBrowserApp::instance());

        m_delegate->setOperator(this);
        m_client->setDelegate(m_delegate);
    }

    return 0;
}


void QCefOsrWidget::setFocus(Qt::FocusReason reason)
{
    if (m_browser != nullptr && m_browser->IsValid())
    {
        CefRefPtr<CefBrowserHost> host = m_browser->GetHost();
        if (host != nullptr)
        {
            host->SetFocus(true);
        }
    }
}

QVariant QCefOsrWidget::inputMethodQuery(Qt::InputMethodQuery query) const
{
    QCEF_DEBUG();
    qDebug() << "QCefOsrWidget::inputMethodQuery = " << query;

    QVariant var;

    switch (query)
    {
    case Qt::ImCursorRectangle:
        var = QVariant(m_imeCursorRect);
        qDebug() << "QCefOsrWidget::inputMethodQuery->ImCursorRectangle = " << m_imeCursorRect;
    case Qt::ImAnchorRectangle:
        break;
    case Qt::ImFont:
        break;
    case Qt::ImCursorPosition:
        break;
    case Qt::ImSurroundingText:
        break;
    case Qt::ImCurrentSelection:
        break;
    case Qt::ImMaximumTextLength:
        break;
    case Qt::ImAnchorPosition:
        break;
    default:
        break;
    }

    if (var.isValid())
    {
        return var;
    }

    //
    return QWidget::inputMethodQuery(query);
}

void QCefOsrWidget::inputMethodEvent(QInputMethodEvent * event)
{
    QCEF_DEBUG();
    qDebug() << "QCefOsrWidget::inputMethodEvent = " << event;

    if (m_browser == nullptr || !m_browser->IsValid())
    {
        return;
    }

    auto composingText = event->preeditString();
    auto composedText = event->commitString();

    if (!composedText.isEmpty())
    {
        m_browser->GetHost()->ImeCommitText(composedText.toStdString(), CefRange(UINT32_MAX, UINT32_MAX), 0);
    }
    else if (!composingText.isEmpty())
    {
        CefCompositionUnderline underline;
        underline.background_color = 0;
        underline.range = { 0, (int)composingText.length() };

        CefRange selectionRange;
        for (auto& attr : event->attributes())
        {
            switch (attr.type)
            {
            case QInputMethodEvent::TextFormat:
                break;
            case QInputMethodEvent::Cursor:
                selectionRange.Set(attr.start, attr.start);
                break;
            case QInputMethodEvent::Language:
            case QInputMethodEvent::Ruby:
            case QInputMethodEvent::Selection:
                break;
            default:
                break;
            }
        }

        m_browser->GetHost()->ImeSetComposition(composingText.toStdString(), { underline }, CefRange(UINT32_MAX, UINT32_MAX), selectionRange);
    }
    else
    {
        m_browser->GetHost()->ImeCancelComposition();
    }
}

void QCefOsrWidget::showEvent(QShowEvent *event)
{
    if (m_browser != nullptr && m_browser->IsValid())
    {
        m_browser->GetHost()->WasHidden(false);
    }

    //
    QWidget::showEvent(event);
}

void QCefOsrWidget::hideEvent(QHideEvent *event)
{
    if (m_browser != nullptr && m_browser->IsValid())
    {
        m_browser->GetHost()->WasHidden(true);
    }

    //
    QWidget::hideEvent(event);
}

void QCefOsrWidget::focusInEvent(QFocusEvent *event)
{
    if (m_browser != nullptr && m_browser->IsValid())
    {
        m_browser->GetHost()->SetFocus(true);
    }

    //
    QWidget::focusInEvent(event);
}

void QCefOsrWidget::focusOutEvent(QFocusEvent *event)
{
    if (m_browser != nullptr && m_browser->IsValid())
    {
        m_browser->GetHost()->SetFocus(false);
    }

    //
    QWidget::focusOutEvent(event);
}

void QCefOsrWidget::keyPressEvent(QKeyEvent *event)
{
    sendCefKeyEvent(event);

    //
    QWidget::keyPressEvent(event);
}

void QCefOsrWidget::keyReleaseEvent(QKeyEvent *event)
{
    sendCefKeyEvent(event);

    //
    QWidget::keyReleaseEvent(event);
}

void QCefOsrWidget::sendCefKeyEvent(QKeyEvent* event)
{
    if (m_browser == nullptr || !m_browser->IsValid())
    {
        return;
    }

    CefKeyEvent e;
    MapQKeyEventToCefKeyEvent(event, e);

       // QEvent::KeyRelease - send key release event
    if (event->type() == QEvent::KeyRelease)
    {
        e.type = KEYEVENT_KEYUP;
        m_browser->GetHost()->SendKeyEvent(e);
        return;
    }

       // QEvent::KeyPress - send key down event
    e.type = KEYEVENT_RAWKEYDOWN;
    m_browser->GetHost()->SendKeyEvent(e);

       // send key char event
    if (ShouldSendKeyCharEvent(event))
    {
        AdjustCefKeyCharEvent(event, e);
        e.type = KEYEVENT_CHAR;
        m_browser->GetHost()->SendKeyEvent(e);
    }
}

void QCefOsrWidget::mousePressEvent(QMouseEvent *event)
{
    sendCefMouseEvent(event);

    //
    QWidget::mousePressEvent(event);
}

void QCefOsrWidget::mouseReleaseEvent(QMouseEvent *event)
{
    sendCefMouseEvent(event);

    //
    QWidget::mouseReleaseEvent(event);
}

void QCefOsrWidget::mouseMoveEvent(QMouseEvent *event)
{
    sendCefMouseEvent(event);

    //
    QWidget::mouseMoveEvent(event);
}

void QCefOsrWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
    sendCefMouseEvent(event);

    //
    QWidget::mouseDoubleClickEvent(event);
}

void QCefOsrWidget::sendCefMouseEvent(QMouseEvent *event)
{
    if (m_browser == nullptr || !m_browser->IsValid())
    {
        return;
    }

    auto b = event->buttons();
    auto m = event->modifiers();

    CefMouseEvent e;
    e.x = ((QMouseEvent*)event)->pos().x();
    e.y = ((QMouseEvent*)event)->pos().y();
    e.modifiers |= m & Qt::ControlModifier ? EVENTFLAG_CONTROL_DOWN : 0;
    e.modifiers |= m & Qt::ShiftModifier ? EVENTFLAG_SHIFT_DOWN : 0;
    e.modifiers |= m & Qt::AltModifier ? EVENTFLAG_ALT_DOWN : 0;
    e.modifiers |= b & Qt::LeftButton ? EVENTFLAG_LEFT_MOUSE_BUTTON : 0;
    e.modifiers |= b & Qt::RightButton ? EVENTFLAG_RIGHT_MOUSE_BUTTON : 0;
    e.modifiers |= b & Qt::MiddleButton ? EVENTFLAG_MIDDLE_MOUSE_BUTTON : 0;

    if (QEvent::MouseMove == event->type())
    {
        m_browser->GetHost()->SendMouseMoveEvent(e, false);
        return;
    }

    CefBrowserHost::MouseButtonType mbt = MBT_LEFT;
    switch (event->button())
    {
    case Qt::LeftButton:
        mbt = MBT_LEFT;
        break;
    case Qt::RightButton:
        mbt = MBT_RIGHT;
        break;
    case Qt::MiddleButton:
        mbt = MBT_MIDDLE;
        break;
    default:
        break;
    }

    if (QEvent::MouseButtonPress == event->type())
    {
        m_browser->GetHost()->SendMouseClickEvent(e, mbt, false, 1);
    }
    else if (QEvent::MouseButtonDblClick == event->type())
    {
        m_browser->GetHost()->SendMouseClickEvent(e, mbt, false, 2);
    }
    else if (QEvent::MouseButtonRelease == event->type())
    {
        // if the release was generated right after a popup, we must discard it
        m_browser->GetHost()->SendMouseClickEvent(e, mbt, true, 1);
    }
}

void QCefOsrWidget::wheelEvent(QWheelEvent *event)
{
    if (m_browser == nullptr || !m_browser->IsValid())
    {
        return;
    }

    auto p = event->position();
    auto d = event->angleDelta();
    auto m = event->modifiers();
    auto b = event->buttons();

    CefMouseEvent e;
    e.modifiers |= m & Qt::ControlModifier ? EVENTFLAG_CONTROL_DOWN : 0;
    e.modifiers |= m & Qt::ShiftModifier ? EVENTFLAG_SHIFT_DOWN : 0;
    e.modifiers |= m & Qt::AltModifier ? EVENTFLAG_ALT_DOWN : 0;
    e.modifiers |= b & Qt::LeftButton ? EVENTFLAG_LEFT_MOUSE_BUTTON : 0;
    e.modifiers |= b & Qt::RightButton ? EVENTFLAG_RIGHT_MOUSE_BUTTON : 0;
    e.modifiers |= b & Qt::MiddleButton ? EVENTFLAG_MIDDLE_MOUSE_BUTTON : 0;

    e.x = p.x();
    e.y = p.y();
    m_browser->GetHost()->SendMouseWheelEvent(e, m & Qt::ShiftModifier ? 0 : d.x(), m & Qt::ShiftModifier ? 0 : d.y());

    //
    QWidget::wheelEvent(event);
}


void QCefOsrWidget::paintEvent(QPaintEvent *event)
{
    QCEF_DEBUG();

    // 1. construct painter for current widget
    QPainter painter(this);

    // 2. paint background with background role
    // for OSR mode, this makes sure the surface will be cleared before a new drawing
    // for NCW mode, this makes sure QCefView will not be treated as transparent background
    painter.fillRect(rect(), palette().color(backgroundRole()));

    //#if defined(CEF_USE_OSR)
    if (true)
    {
        // 3. paint widget with its stylesheet
        QStyleOption opt;
        opt.initFrom(this);
        style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);

        // 4. paint the CEF view and popup
        // get current scale factor
        qreal scaleFactor = devicePixelRatioF();

        // perform the painting
        // paint cef view
        int width = m_image.width() / scaleFactor;
        int height = m_image.height() / scaleFactor;
        painter.drawImage(QRect{ 0, 0, width, height }, m_image);

        // paint cef popup
        if (m_popupShow)
        {
            painter.drawImage(m_popupRect, m_popupImage);
        }
    }
    //#endif

    // 5. call base paintEvent (empty implementation)
    QWidget::paintEvent(event);
}

void QCefOsrWidget::onPageCursorChange(int type)
{
    QCursor cur;
    if (type != CT_CUSTOM)
    {
        cur.setShape(mapCursorShape(type));
    }

    this->setCursor(cur);
}

void QCefOsrWidget::onPagePopupShow(bool show)
{
    m_popupShow = show;
    m_popupImage = QImage();
    m_popupRect = QRect();
}

void QCefOsrWidget::onPagePopupSize(QRect rt)
{
    m_popupRect = rt;
}

void QCefOsrWidget::onPagePaint(int type, QRegion region, QImage image, int width, int height)
{
    //...
    if (PET_VIEW == type)
    {
        if (m_image.size() != image.size())
        {
            // update full image
            m_image = image;
        }
        else
        {
            // update only dirty regions
            QPainter painter(&m_image);
            for (auto& rc : region)
            {
                painter.drawImage(rc, image, rc);
            }
        }
    }
    else if (PET_POPUP == type)
    {
        if (m_popupImage.size() != image.size())
        {
            m_popupImage = image;
        }
        else
        {
            // update region
            QPainter painter(&m_popupImage);
            for (auto& rc : region)
            {
                painter.drawImage(rc, image, rc);
            }
        }
    }

    //
    this->update();
}

void QCefOsrWidget::onPageAcceleratedPaint(int type, QRegion region, void * shared_handle)
{

}

void QCefOsrWidget::onPageScrollOffsetChanged(double x, double y)
{

}

void QCefOsrWidget::onPageImeCompositionRangeChanged(int from, int to, QRect rt)
{
    m_imeCursorRect = rt;
    QInputMethod * inputMethod = QGuiApplication::inputMethod();
    if (inputMethod)
    {
        inputMethod->update(Qt::ImCursorRectangle);
    }
}

void QCefOsrWidget::onPageTextSelectionChanged(int from, int to, QString strSelectedText)
{

}

void QCefOsrWidget::onPageVirtualKeyboardRequested(int input_mode)
{

}

void QCefOsrWidget::onPageFocusedEditableNodeChanged(bool focusOnEditableField)
{
    this->setAttribute(Qt::WA_InputMethodEnabled, focusOnEditableField);
}

